/*MyPhotoIndex Free Photo Organizer <www.myphotoindex.com>
  Copyright (C) <2006-2009>  <Asaf Yarkoni asaf@yarkoni.net>

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.*/
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Drawing.Imaging;
using MyPhotoIndex.Utilities;
using System.Windows.Forms;
using MyPhotoIndex.Interfaces;
using MyPhotoIndex.Plugins.ImageFormatProviders;
using MyPhotoIndex.ImageBank.Properties;
using System.Threading;
using MyPhotoIndex.Interfaces.Imaging;
using MyPhotoIndex.Interfaces.ImageFormatProviders;
using System.Xml.Serialization;

namespace MyPhotoIndex.ImageBank
{
    [Serializable]
    public class ImageData : IImageData 
    {
        public event EventHandler ImageDataChangedEvent;

        private int                     m_id;
        private string                  m_fileName;
        private string                  m_fileExtention;
        [NonSerialized]
        private IImageFormatProvider    m_formatProvider;
        private string                  m_importBatchNumber;
        private ImageLinkStatus         m_linkStatus;
        private Size                    m_imageSize;
        private Size                    m_thumbnailSize;
        private Image                   m_thumbnail;
        private int                     m_thumbnailRef;
        private string                  m_comment;
        private int[]                   m_tags = new int[0];
        private DateTime                m_datePictureTaken;
        private DateTime                m_dateDigitized;
        private int                     m_rating;
        private bool                    m_isVideo;
        private bool                    m_canRotate;
        private bool                    m_encrypted;
        private String                  m_fileSignature;
        private DateTime                m_lastUpdated;
        private int[]                   m_categories = new int[0];
        private int                     m_versionParentId = -1;
        private DateTime                m_versionDate;
        private int                     m_versionsCount = 0;
        private string                  m_sourceLabel;

        public ImageData()
        {
        }

        internal ImageData(int Id)
        {
            m_id = Id;
        }

        internal ImageData(IImageData other)
        {
            Id = other.Id;
            FileName = other.FileName;
            ImportBatchNumber = other.ImportBatchNumber;
            ImageSize = other.ImageSize;
            ThumbnailSize = other.ThumbnailSize;
            Comment = other.Comment;
            AddTags(other.Tags);
            DateTaken = other.DateTaken;
            DateDigitized = other.DateDigitized;
            Rating = other.Rating;
            FileSignature = other.FileSignature;
            LastModified = other.LastModified;
            AddCategories(other.Categories);
            VersionParentId = other.VersionParentId;
            VersionDate = other.VersionDate;
        }

        public Object SyncLock
        {
            get { return this; }
        }

        public int Id
        {
            get { return m_id; }
            set { m_id = value; }
        }

        public String FileSignature
        {
            get { return m_fileSignature; }
            set { m_fileSignature = value; }
        }

        public string ImportBatchNumber
        {
            get { return m_importBatchNumber; }
            set { m_importBatchNumber = value; }
        }

        public DateTime DateTaken
        {
            get { return m_datePictureTaken; }
            set { m_datePictureTaken = value; }
        }

        public DateTime DateDigitized
        {
            get {return m_dateDigitized; }
            set { m_dateDigitized = value; }
        }

        [XmlIgnore]
        public Image Thumbnail
        {
            get 
            {
                if (m_thumbnail == null)
                {
                    m_thumbnail = Storage.Instance.GetThumbnail(this);
                    m_thumbnailSize = new Size(m_thumbnail.Width, m_thumbnail.Height);

                }

                return m_thumbnail; 
            }

            set 
            {
                lock (this)
                {
                    if (m_thumbnail != null)
                    {
                        m_thumbnail.Dispose();
                        m_thumbnail = null;
                        m_thumbnailSize = Size.Empty;
                    }

                    if (value != null)
                    {
                        m_thumbnail = value;
                        m_thumbnailSize = new Size(m_thumbnail.Width, m_thumbnail.Height);
                    }
                }
            }
        }

        [XmlIgnore]
        public int ThumbnailReferences
        {
            get
            {
                return m_thumbnailRef;
            }
            set
            {
                m_thumbnailRef = value;
                if (m_thumbnailRef == 0)
                {
                    Thumbnail = null;
                }
            }
        }

        public string FileName
        {
            get 
            {
                //if (m_fileName.StartsWith(@"M:\Images") == true)
                //{
                //    return m_fileName.Replace(@"M:\Images", @"E:\Backup\Media\Images");
                //}

                return m_fileName; 
            }
            set 
            { 
                m_fileName = value;

                string extention = Path.GetExtension(m_fileName);
                if (string.IsNullOrEmpty(extention) == false)
                {
                    m_fileExtention = extention.Remove(0, 1);
                    if (string.IsNullOrEmpty(m_fileExtention) == false)
                    {
                        m_formatProvider = ImageFormatProviders.Default[m_fileExtention];
                        m_isVideo = m_formatProvider.IsVideo;
                        m_canRotate = m_formatProvider.CanRotate;
                        IEncryptedFormatProvider ep = m_formatProvider as IEncryptedFormatProvider;
                        if (ep != null)
                        {
                            m_encrypted = true;
                        }
                        else
                        {
                            m_encrypted = false;
                        }
                    }
                }
            }
        }

        public string FileExtention
        {
            get { return m_fileExtention; }
        }

        public IImageFormatProvider FormatProvider
        {
            get { return m_formatProvider; }
        }

        public ImageLinkStatus LinkStatus
        {
            get { return m_linkStatus; }
            set { m_linkStatus = value; }
        }

        public Size ImageSize
        {
            get { return m_imageSize; }
            set { m_imageSize = value; }
        }

        [XmlIgnore]
        public Size ThumbnailSize
        {
            get { return m_thumbnailSize; }
            set { m_thumbnailSize = value; }
        }

        public string Comment
        {
            get { return m_comment; }
            set { m_comment = value; }
        }

        public int[] Tags
        {
            get { return m_tags; }
            set { m_tags = value; }
        }

        public void AddTags(int[] tagIds)
        {
            int[] oldArray = m_tags;
            m_tags = new int[oldArray.Length + tagIds.Length];
            oldArray.CopyTo(m_tags, 0);

            int i = 0, j = oldArray.Length;
            for (i = 0; i < tagIds.Length; i++)
            {
                m_tags[j++] = tagIds[i];
            }
        }

        public void RemoveTag(int tagId)
        {
            int[] oldArray = m_tags;
            m_tags = new int[oldArray.Length - 1];
            int j = 0;
            for (int i = 0; i < oldArray.Length; i++)
            {
                if (oldArray[i] != tagId)
                {
                    m_tags[j++] = oldArray[i];
                }
            }
        }

        public void ClearTags()
        {
            m_tags = new int[0];
        }

        public bool TagExists(int tagId)
        {
            for (int i = 0; i < m_tags.Length; i++)
            {
                if (m_tags[i] == tagId)
                {
                    return true;
                }
            }

            return false;
        }

        public int Rating
        {
            get { return m_rating; }
            set { m_rating = value; }
        }

        [XmlIgnore]
        public bool IsVideo
        {
            get { return m_isVideo; }
            set { m_isVideo = value; }
        }

        [XmlIgnore]
        public bool CanRotate
        {
            get { return m_canRotate; }
            set { m_canRotate = value; }
        }

        public bool Encrypted
        {
            get { return m_encrypted; }
        }

        public string CameraMaker
        {
            get { throw new Exception("The method or operation is not implemented."); }
        }

        public string CameraModel
        {
            get { throw new Exception("The method or operation is not implemented."); }
        }

        public string SoftwareUsed
        {
            get { throw new Exception("The method or operation is not implemented."); }
        }

        public float Aperture
        {
            get { throw new Exception("The method or operation is not implemented."); }
        }

        public String ExposureTime
        {
            get { throw new Exception("The method or operation is not implemented."); }
        }

        public CameraExposureProgram ExposureProgram
        {
            get { throw new Exception("The method or operation is not implemented."); }
        }

        public int IsoSpeed
        {
            get { throw new Exception("The method or operation is not implemented."); }
        }

        public int ExposureCompensation
        {
            get { throw new Exception("The method or operation is not implemented."); }
        }

        public CameraMeteringMode MeteringMode
        {
            get { throw new Exception("The method or operation is not implemented."); }
        }

        public ImageLightSource LightSource
        {
            get { throw new Exception("The method or operation is not implemented."); }
        }

        public CameraFlashStatus FlashStatus
        {
            get { throw new Exception("The method or operation is not implemented."); }
        }

        public float FocalLength
        {
            get { throw new Exception("The method or operation is not implemented."); }
        }

        public DateTime LastModified
        {
            get
            {
                return m_lastUpdated;
            }
            set
            {
                m_lastUpdated = value;
            }
        }

        public int[] Categories
        {
            get { return m_categories; }
            set { m_categories = value; }
        }

        public int VersionParentId
        {
            get { return m_versionParentId; }
            set { m_versionParentId = value; }
        }

        public DateTime VersionDate
        {
            get { return m_versionDate; }
            set { m_versionDate = value; }
        }

        public int VersionsCount
        {
            get { return m_versionsCount; }
            set { m_versionsCount = value; }
        }

        public string SourceLabel
        {
            get { return m_sourceLabel; }
            set { m_sourceLabel = value; }
        }

        public void AddCategories(int[] categoryIds)
        {
            int[] oldArray = m_categories;
            m_categories = new int[oldArray.Length + categoryIds.Length];
            oldArray.CopyTo(m_categories, 0);
            int i = 0, j = oldArray.Length;
            for (i = 0; i < categoryIds.Length; i++)
            {
                m_categories[j++] = categoryIds[i];
            }          
        }

        public void RemoveCategory(int categoryId)
        {
            int[] oldArray = m_categories;
            m_categories = new int[oldArray.Length - 1];
            int j = 0;
            for (int i = 0; i < oldArray.Length; i++)
            {
                if (oldArray[i] != categoryId)
                {
                    m_categories[j++] = oldArray[i];
                }
            }
        }

        public void ClearCategories()
        {
            m_categories = new int[0];
        }

        public bool CategoryExists(int tagId)
        {
            for (int i = 0; i < m_categories.Length; i++)
            {
                if (m_categories[i] == tagId)
                {
                    return true;
                }
            }

            return false;
        }

        public void InvokeChangedEvent()
        {
            if (ImageDataChangedEvent != null)
            {
                ImageDataChangedEvent.Invoke(this, EventArgs.Empty);
            }
        }

        public void Rotate(RotateFlipType rotationType)
        {
            lock (this)
            {
                IImageFormatProvider provider = this.FormatProvider;

                Image thumbnail = null;
                Size imageSize = Size.Empty;
                provider.Rotate(this.FileName, rotationType, out thumbnail, out imageSize);
                this.ImageSize = imageSize;

                Thumbnail = thumbnail;
                ThumbnailSize = new Size(thumbnail.Width, thumbnail.Height);

                Update(true, true);

                InvokeChangedEvent();
            }
        }

        public void Delete(bool deleteFromDisk, bool sendToRecycleBin)
        {
            Storage.Instance.Remove(this.Id, deleteFromDisk/*, sendToRecycleBin*/);
        }

        public void Update(bool updateExif, bool updateThumbnail)
        {
            Storage.Instance.UpdateImageData(this, updateExif, updateThumbnail);
        }

        public virtual void LoadImage(Size requestedSize, ImageLoadDelegate imageLoadDelegate)
        {
            Storage.Instance.LoadImageFromDisk(this, requestedSize, imageLoadDelegate);
        }

        public Image CreateThumbnail(Size size)
        {
            if (LinkStatus == ImageLinkStatus.Invalid)
            {
                return new Bitmap(Resources.NoThumbnail);
            }

            try
            {
                return FormatProvider.GetThumbnail(FileName, size);
            }
            catch (DirectoryNotFoundException dex)
            {
                LinkStatus = ImageLinkStatus.Invalid;
                Logger.Log(dex);
            }
            catch (FileNotFoundException fex)
            {
                LinkStatus = ImageLinkStatus.Invalid;
                Logger.Log(fex);
            }
            catch (Exception ex)
            {
                Logger.Log(ex);
            }

            return null;
        }

        #region IDisposable Members

        ~ImageData()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (m_thumbnail != null)
                {
                    m_thumbnail.Dispose();
                }
            }
        }

        #endregion

        public override string ToString()
        {
            String format = "m_id={0}\nm_fileName={1}\nm_linkStatus={2}m_imageSize={3}\n" +
                          "m_thumbnailSize={4}\nm_Tags={5}\n";

            Object[] data = new object[] { m_id, m_fileName, m_linkStatus, m_imageSize, m_thumbnailSize, m_tags.Length };

            return String.Format(format, data);
        }
    }
}
